package smtp;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

import javax.mail.Flags.Flag;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.event.MessageCountEvent;
import javax.mail.event.MessageCountListener;
import javax.swing.DefaultListModel;
import javax.swing.JList;
import javax.swing.ListModel;

import com.sun.mail.imap.IMAPFolder;
import com.sun.mail.imap.IMAPMessage;
import com.sun.mail.imap.IMAPStore;

/*
 * To do:
 * 
 * Notification or chime when email is received? (When list updates)
 * 
 * Supply a place to plug in folder operations such as expunge, mark unread, and move? (Open message?)
 * 
 * Listener to the size has to take care of selection changes?  Or those will work out automatically?
 * 
 * Prevent going into idle mode if not connected?  Support offline message reading?
 * 
 */

public class FolderController {

	IMAPStore store; //store to which this folder belongs

	IMAPFolder currentFolder;
	IMAPFolder trash; //trash folder to move to, else delete NOT IMPLEMENTED

	Vector<IMAPMessage> viewingMail; // this could be an array instead (the array we discussed as a group)
	Vector<IMAPMessage> allMail;
	Comparator<IMAPMessage> messageComparator;

	int pageNumber;
	int itemsPerPage = 30; // default value

	JList jlist;

	ActionListener expungeAction = null;
	ActionListener markUnreadAction = null;
	ActionListener messageCount;

	// A display class is not needed like for the Mailbox controller because display is handled by email list cell renderer

	/**
	 * Constructor.
	 * Takes the IMAP folder to manage and the JList whose ListModel it will update as needed with the folder's contents.
	 */
	public FolderController(IMAPFolder folder, JList jl) throws FileNotFoundException{
		currentFolder = folder;
		jlist = jl;

		// open folder if it is not already open
		try {
			if(currentFolder.exists()){
				if (currentFolder.isOpen()){
					currentFolder.close(false);
					System.out.println("Folder " + currentFolder + " was openned before expected.  This shouldn't happen. " +
					"Closing without expunge and reopenning...");
				}
				currentFolder.open(IMAPFolder.READ_ONLY);  //for testing purposes.  change to READ_WRITE later
			} else {
				throw new FileNotFoundException();
			}
		} catch (MessagingException e1) {
			System.out.println("Exception in FolderController: constructor: current folder " + currentFolder);
			e1.printStackTrace();
		}


		// create the allMail object
		// create a comparator for the IMAPMessages in allMail
		// instantiate viewingMail to view the first page
		// add a MessageCountListener to update allMail when changes occur in the currentFolder
		initializeList();

		// set idle until some action is called
		idle();
		System.out.println("Idling.");

		System.out.println("FolderController created.");
	}

	/**
	 * This method is synchronized so IMAP folder updates do not occur while constructing
	 * the allMail and viewingMail objects
	 * 
	 * In this method we:
	 * 1) Create the allMail object
	 * 2) create a comparator for IMAPMessages in allMail
	 * 3) Instantiate viewingMail to view the first page
	 * 4) Add a MessageCountListener to update allMail when changes occur in the currentFolder
	 * 
	 */
	private synchronized void initializeList(){
		// create allMail
		allMail = new Vector<IMAPMessage>();
		Message[] messages = null;
		try {			
			messages = currentFolder.getMessages();

		} catch (MessagingException e1) {
			System.out.println("Exception in FolderController: initializeList: could not get messages of folder " + currentFolder);
			e1.printStackTrace();
		}

		for(Message message: messages){
			allMail.add((IMAPMessage)message);
		}

		// ** Create comparator for IMAPMessages **
		// comparator compares the UID number of the messages.
		// UID of a newer messages will always be greater than that of an older message
		messageComparator = new Comparator<IMAPMessage>(){

			/**
			 * 
			 * @param mes1
			 * @param mes2
			 * @return Negative value, 0, or positive value indicating
			 * whether mes1 is less than, equal to, to greater than mes2
			 */
			@Override
			public int compare(IMAPMessage mes1, IMAPMessage mes2) {
				int val = 0;
				try {
					val = ((int)(currentFolder.getUID(mes1) - currentFolder.getUID(mes2)));
				} catch (MessagingException e) {
					System.out.println("Exception in FolderController: initializeList: could not get message UID to compare.\n" +
							"Message 1: " + mes1 + "\n" +
							"Message 2: " + mes1);
					e.printStackTrace();
				}
				return val;
			}

		};

		// ** Instantiate viewingMail to view the first page **
		pageNumber = 1;
		updateMailView();

		System.out.println("View created for the first time.");


		// ** Add a MessageCountListener to update allMail **
		// (and as needed viewingMail) when changes occur in the currentFolder
		currentFolder.addMessageCountListener(new MessageCountListener(){

			@Override
			public synchronized void messagesAdded(MessageCountEvent e) {
				// add the messages the system has just received
				// if viewingMail is changed, a list content update event should be triggered automatically so the UI displays correctly

				IMAPMessage[] messages = (IMAPMessage[])(e.getMessages());

				// add each message to allMail
				for (IMAPMessage message : messages){
					// often added messages will be new and thus added to the bottom, so first check if the message belongs there
					if ((allMail.isEmpty()) || (messageComparator.compare(allMail.lastElement(), message) > 0)){
						allMail.addElement(message);
					} else {
						int search = Collections.binarySearch(allMail, message, messageComparator);
						// if this message does not already exist (it shouldn't!), then add it to allMail
						if(search < 0) {
							// inserts the element at this index
							// search = (-(insertion point) - 1), so insertion point = (-(search) - 1)
							allMail.insertElementAt(message, (-search - 1)); 
						}
					}
				}

				// UPDATE VIEWING MAIL IF
				// We don't want to just update it  no matter what because it will reset the current selection
				// The current display has been affected if:
				// IF (viewed messages was less than full potential) OR (initial index is different) OR (final index is different)
				if((viewingMail.size() < itemsPerPage) || !(viewingMail.get(0).equals(allMail.get(itemsPerPage*(pageNumber-1))))
						|| !(viewingMail.get(itemsPerPage).equals(allMail.get(itemsPerPage*(pageNumber))))){

					// UPDATE
					updateMailView();
				}

			}

			@Override
			public synchronized void messagesRemoved(MessageCountEvent e) {
				// if the user calls an explicit expunge e.isRemoved() will be true.
				// Do we need to distinguish this?  We never call messages by their sequence number, so no.

				// remove the messages that have just been expunged
				// if viewingMail is changed, a list content update event should be triggered automatically so the UI displays correctly

				IMAPMessage[] messages = (IMAPMessage[])(e.getMessages());

				// remove each message from allMail
				for (IMAPMessage message : messages){
					// index of the message to remove
					int index = Collections.binarySearch(allMail, message, messageComparator);
					// if this message exists (it always should!), then remove it from allMail
					if(index >= 0) {
						allMail.removeElementAt(index); 
					} else {
						System.out.println("FolderController: MessageCountListener: messagesRemoved: " +
								"This message mysteriously dissappeared!: " + message);
					}
				}

				// UPDATE VIEWING MAIL IF
				// We don't want to just update it  no matter what because it will reset the current selection
				// The current display has been affected if:
				// IF (the # of viewed messages will be less than full potential) OR (initial index is different) OR (final index is different)
				if(allMail.size() < ((pageNumber)*itemsPerPage) || !(viewingMail.get(0).equals(allMail.get(itemsPerPage*(pageNumber-1))))
						|| !(viewingMail.get(itemsPerPage).equals(allMail.get(itemsPerPage*(pageNumber))))){
					// IF in addition to condition #1 above being met, there will be no messages on current page
					if(allMail.size() < ((pageNumber-1)*itemsPerPage)){
						pageNumber = (allMail.size()/itemsPerPage) + 1; // + 1 because pageNumber index starts at 1
					}

					// UPDATE
					updateMailView();

				}
			}



		});

		System.out.println("Init method completed.");
	}

	/**
	 * Private helper method.  This method update viewingMail to reflect the values of
	 * the current page number, items per page, and mail contained in the allMail vector
	 */
	private void updateMailView(){
		List<IMAPMessage> sublist = null;
		if(allMail.size() > 0){
			// set a new sequence of messages into the viewingMail
			// allMail index: from starting index for this page (sure to exist)
			// to the minimum of (total messages AND expected index for maximum messages for this page)
			sublist = allMail.subList(itemsPerPage*(pageNumber-1), Math.min(allMail.size(), itemsPerPage*pageNumber));
		}
		
		viewingMail = new Vector<IMAPMessage> (sublist);
		jlist.setListData(viewingMail);
	}

	/**
	 * Changes the page to the indicated page number.
	 * If the page number does not fall within the valid range, then the jlist is not updated and a false boolean is returned.
	 * 
	 * @param pageNum
	 * @return - whether the jlist was changed to the indicated page number
	 */
	public boolean changePage (int pageNum){
		boolean changed = false;

		// synchronized so that allMail doesn't get updated during these steps
		synchronized(this){
			// pageNum must be greater than zero and less than the maximum page containing items when
			// there are <itemsPerPage> number of items per page and <allMail.size()> total items
			if ((0 < pageNum) && (pageNum <= (allMail.size()/itemsPerPage))){
				changed = true;
				pageNumber = pageNum;
				updateMailView();
			}
		}

		return changed;
	}

	/**
	 * Closes the folder without expunging messages marked for deletion.
	 * Call before switching folders.
	 * Call before renaming folder.
	 */
	public void close(){
		try {
			currentFolder.close(false);
		} catch (MessagingException e) {
			System.out.println("Exception in FolderController: close: could not close folder " + currentFolder);
			e.printStackTrace();
		}
	}

	public void setIdle(){
		idle();
	}

	public ActionListener getExpungeSelectedListener(){
		if (expungeAction == null){
			expungeAction = new ActionListener(){

				@Override
				public void actionPerformed(ActionEvent e) {
					JList jlist = ((JList)e.getSource());
					//assumes all the items in the list are IMAPMessages
					IMAPMessage[] messages = ((IMAPMessage[])jlist.getSelectedValues());

					/* THIS IS WHERE MOVE TO TRASH FOLDER WOULD GO.  Not currently implemented */


					for(IMAPMessage message: messages){
						try {
							message.setFlag(Flag.DELETED, true);
						} catch (MessagingException e1) {
							System.out.println("FolderController: getExpungeSelectedListener: Exception setting flag to delete of message: " + message);
							e1.printStackTrace();
						}
					}

					// this will generate a MessageCountEvent displayed to MessageCountListeners
					try {
						currentFolder.expunge(messages);
					} catch (MessagingException e1) {
						System.out.println("FolderController: getExpungeSelectedListener: Exception deleting messages: " + messages);
						e1.printStackTrace();
					}

					idle();
				}

			};
		}
		return expungeAction;
	}

	public void addSelectMessageListener(ActionListener al) {
		
	}
	
	public void delete(){
		
	}
	
	public void markUnread(){
		
	}
	
	public void changeFolder(){
		// SAVE FOR LATER
	}
	
	public ActionListener getMarkUnreadListener(){
		if (markUnreadAction == null){
			markUnreadAction = new ActionListener(){

				@Override
				public void actionPerformed(ActionEvent e) {
					JList jlist = ((JList)e.getSource());
					//assumes all the items in the list are IMAPMessages
					IMAPMessage[] messages = ((IMAPMessage[])jlist.getSelectedValues());

					for(IMAPMessage message: messages){
						try {
							message.setFlag(Flag.SEEN, false);
						} catch (MessagingException e1) {
							System.out.println("FolderController: getMarkUnreadListener: Exception setting flag to seen of message: " + message);
							e1.printStackTrace();
						}
					}
				}

			};
		}
		return markUnreadAction;
	}

	private void idle(){
		new Thread(new Runnable(){

			@Override
			public void run() {
				try {
					currentFolder.idle();
				} catch (MessagingException e) {
					System.out.println("FolderController: idle: messaging exception while idling.");
					e.printStackTrace();
				}				
			}

		});
	}

}